package cn.school.newExam.utils;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

@SuppressWarnings("all")
public class ExcelApi<T> {

    private Workbook workbook;

    private Sheet sheet;

    private String sheetName;

    private int rowNum;

    private List<Excel> fields;

    private Class<T> clazz;

    public ExcelApi(Class<T> clazz) {
        this.clazz = clazz;
    }

    private void init(String sheetName) {
        this.sheetName = sheetName;
        createExcelFields();
        createWorkbook();
    }

    private void createExcelFields() {
        this.fields = getFields();
    }

    private List<Excel> getFields() {
        List<Excel> fields = new ArrayList<>();
        List<Field> tempFields = new ArrayList<>();
        tempFields.addAll(Arrays.asList(this.clazz.getFields()));
        tempFields.addAll(Arrays.asList(this.clazz.getDeclaredFields()));
        for (Field tempField : tempFields) {
            if (tempField.isAnnotationPresent(Excel.class)) {
                Excel annotation = tempField.getAnnotation(Excel.class);
                fields.add(annotation);
            }
        }
        return fields.stream().sorted(Comparator.comparing(Excel::index)).collect(Collectors.toList());
    }

    private void createWorkbook() {
        this.workbook = new XSSFWorkbook();
        this.sheet = this.workbook.createSheet(this.sheetName);
    }

    private void createTemplate() {
        Row row = sheet.createRow(this.rowNum);
        for (Excel field : this.fields) {
            Cell cell = row.createCell(field.index());
            cell.setCellValue(field.title());
        }
    }

    public void exportTemplate(HttpServletResponse response, String fileName) {
        response.setContentType("application/vnd.ms-excel;charset=utf-8");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
        this.init(fileName);
        this.createTemplate();
        OutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            this.workbook.write(outputStream);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (this.workbook != null) {
                    this.workbook.close();
                }
//                if(outputStream != null){
//                    outputStream.flush();
//                    outputStream.close();
//                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private Object getCellValue(Cell cell) {
        Object cellValue = "";
        if (cell == null) {
            return cellValue;
        }
        if (cell.getCellType() == Cell.CELL_TYPE_NUMERIC) {
            if (DateUtil.isCellDateFormatted(cell)) {
                cellValue = DateUtil.getJavaDate((Double) cellValue);
            } else {
                long longVal = Math.round(cell.getNumericCellValue());
                Double doubleVal = cell.getNumericCellValue();
                if (Double.parseDouble(longVal + ".0") == doubleVal){
                    cell.setCellType(Cell.CELL_TYPE_STRING);
                    cellValue = String.valueOf(longVal);
                }
                else {
                    cellValue = String.valueOf(cell.getNumericCellValue());
                }
            }
        } else if (cell.getCellType() == Cell.CELL_TYPE_STRING) {
            cellValue = String.valueOf(cell.getStringCellValue());
        } else if (cell.getCellType() == Cell.CELL_TYPE_BOOLEAN) {
            cellValue = String.valueOf(cell.getBooleanCellValue());
        } else if (cell.getCellType() == Cell.CELL_TYPE_FORMULA) {
            cellValue = String.valueOf(cell.getBooleanCellValue());
        } else {
            cellValue = "";
        }
        return cellValue;
    }

    public Map<String,Field> getFieldMap(){
        Map<String,Field> fieldMap = new HashMap<>();
        List<Field> fieldList = new ArrayList<>();
        fieldList.addAll(Arrays.asList(this.clazz.getFields()));
        fieldList.addAll(Arrays.asList(this.clazz.getDeclaredFields()));
        for (Field field : fieldList) {
            if(field.isAnnotationPresent(Excel.class)){
                Excel annotation = field.getAnnotation(Excel.class);
                fieldMap.put(annotation.title(),field);
            }
        }
        return fieldMap;
    }

    public List<T> importExcel(InputStream inputStream){
        List<T> list  = new ArrayList<>();
        try {
            this.workbook = WorkbookFactory.create(inputStream);
            this.sheet = this.workbook.getSheetAt(0);
            Map<String, Field> fieldMap = this.getFieldMap();
            Map<Integer,Object> titleMap = new HashMap<>();
            int colCount = 0;
            for(int r = 0;r < sheet.getPhysicalNumberOfRows();r++){
                Row row = this.sheet.getRow(r);
                if(r == 0){
                    colCount = row.getPhysicalNumberOfCells();
                    for (int i = 0; i < colCount; i++) {
                        Cell cell = row.getCell(i);
                        Object val = getCellValue(cell);
                        titleMap.put(i,val);
                    }
                }else{
//                    T obj = this.clazz.newInstance();
                    Constructor<T> constructor = this.clazz.getConstructor();
                    T obj = constructor.newInstance();
                    for(int i = 0;i < colCount; i++){
                        Cell cell = row.getCell(i);
                        Object val = getCellValue(cell);
                        String fieldName = (String) titleMap.get(i);
                        Field field = fieldMap.get(fieldName);
                        this.setValue(obj,field,val);
                    }
                    list.add(obj);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return list;
    }

    private Object setValue(Object object,Field field,Object val) throws Exception {
        StringBuilder sb = new StringBuilder("set");
        sb.append(field.getName().substring(0,1).toUpperCase());
        sb.append(field.getName().substring(1));
        Method method = object.getClass().getMethod(sb.toString(),field.getType());
        return method.invoke(object,val);
    }
}
